今天主要一樣會偏向於從使用的方式來了解React相關的路由使用方式,不過在這之前還是會先來認識提到React的路由就會看到的兩個名字,分別是react-router和react-router-dom,那廢話不多說了!直接開始我們今天的主題吧!接下來Router學習的部分,一樣會以react-router為主,但是會以從Vue在使用vue-router時,常用的一些用法和使用情境的對照,來了解相同的情境下,在React中可能可以怎麼使用。
對Vue熟悉的朋友,應該對於vue-router都很熟悉,如果會需要使用到路由就一定會使用vue-router。不過在React的世界對於這部分可能有一點點不太一樣,如果下意識透過相同邏輯的命名去查詢react-router的時候,可能會發現不只有react-router,還有react-router-dom,所以React的Route到底是要安裝哪一套呢?這裡就來認識一下react-router和react-router-dom吧!
react-router是react在處理路由的核心Library
,也就是說換頁的功能主要會由react-router來實作,它可以被使用於web和native等不同環境上。
react-router-dom是基於react-router的Library,是react-router擴充適用於web環境的一些用法的Libray
,例如:Link。由於是擴充來符合web使用情境的Library,所以react-router-dom只能使用於web環境上。也就是說如果是使用react native進行APP的開發,就無法使用react-router-dom。也因為react-router-dom是基於react-router的Library,所以只要安裝react-router-dom,就會包含react-router的部分,不需要額外安裝react-router。
在正式進入react-router-dom的使用之前,先來盤點一些使用vue-router時,經常會使用到的用法,接下來就會由這幾個功能下去看使用react-router-dom時,可以怎麼做。
首先,從基本安裝的步驟正式進入學習react-router-dom的環節吧!
可以透過以下指令把react-router-dom安裝到自己的專案中。
# NPM
npm install react-router-dom
# Yarn
yarn add react-router-dom
安裝好react-router-dom後,也會把react-router也安裝好,接下來就直接來看一些常見的使用情境囉!
使用react-router-dom和使用vue-router一樣,都需要設定特定路由對應到的頁面,不過vue-router在這部分的設定上,會是以一個物件下設定相關的內容,而react-router-dom則是在v6之後,才能用類似vue-router的用物件來設定路由,在v5之前都只能用Router、Switch搭配Route的方式把詳細的route內容定義Jsx裡面。
vue-router是用一個陣列搭配物件來定義路由,所以很好管裡,也可以和template上的內容分開,不需要混在一起寫。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'MainPage',
component: MainPage
},
{
path: '/page-a',
name: 'PageA',
component: PageA
},
{
path: '/page-b',
name: 'PageB',
component: PageB
},
]
})
v5的方式:
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/" element={<MainPage />}></Route>
<Route path="page-a" element={<PageA />}></Route>
<Route path="page-b" element={<PageB />}></Route>
</Switch>
</Router>
</div>
);
}
定義完路由後,還要回到入口檔案裡將HashRouter或HisotryRouter包在App外面,定義的路由才可以被套用。
import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
// 在APP外用HashRouter包起來
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
);
v6的方式
如果是將路由定義在jsx裡面,v6版本之後,改成用Routes和Route搭配。
import MainPage from './MainPage';
import PageA from './PageA';
import PageB from './PageB';
function App() {
return (
<div className="App">
<Routes>
<Route path="/" element={<MainPage />}></Route>
<Route path="page-a" element={<PageA />}></Route>
<Route path="page-b" element={<PageB />}></Route>
</Routes>
</div>
);
}
除了上述的定義方法外,還可以用createHashRouter
或是createHistoryRouter
的寫法,透過傳入一個陣列來定義路由。這個方法也就是跟vue-router比較相似的方式,也是我自己個人認為比較好管理的路由設定方式。
使用createHashRouter,搭配陣列物件的寫法,定義路由。
import { createHashRouter } from 'react-router-dom';
import MainPage from '../MainPage';
import PageA from '../PageA';
import PageB from '../PageB';
const Router = createHashRouter([
{
path: '/',
element: <MainPage />
},
{
path: 'page-a',
element: <PageA />
},
{
path: 'page-b',
element: <PageB />
},
]);
export default Router;
一樣需要把設定好的router帶到App裡面,這裡使用RouterProvider把設定好的Router帶上。
import { RouterProvider } from 'react-router-dom';
import Router from './router';
function App() {
return (
<div className="App">
<RouterProvider router={Router} />
</div>
);
}
目前這樣的寫法,root.render的地方不需要加上HashRouter。
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
如果想要調整為嵌套路由,原本就使用物件進行設定的vue-router的設定方式是比較簡單易懂的,而react-router-dom的寫法則是依照是把路由設定在jsx裡面的方法,還是用陣列來設定的方式而有不同。
可以透過children屬性來定義嵌套的路由。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
// 略...
{
// 定義嵌套的route,並且定義嵌套route的Layout
path: '/parent',
component: Layout,
children: [
{
path: '',
name: 'MainChild',
component: MainChild,
},
// 略...
]
},
]
})
被當為嵌套路由的父元件中,則需要加上RouterView
,來顯示子路由的內容。
<template>
<div>
<RouterView />
</div>
</template>
將route的設定內容直接寫在jsx的寫法。
function App() {
return (
<div className="App">
<Routes>
<Route path="/" element={<MainPage />}></Route>
<Route path="page-a" element={<PageA />}></Route>
<Route path="page-b" element={<PageB />}></Route>
// 定義嵌套的route,並且定義嵌套route的Layout
<Route path="parent" element={<Layout />}>
<Route index element={<MainChild />} />
<Route path="child-a" element={<ChildA />} />
<Route path="child-b" element={<ChildB />} />
</Route>
</Routes>
</div>
);
}
嵌套route用物件定義的寫法跟vue-router的寫法很相似,一樣是用children屬性來設定嵌套路由。
const router = createHashRouter([
// 略...
{
// 定義嵌套的route,並且定義嵌套route的Layout
path: 'parent',
element: <Layout />,
children: [
{
path: '',
element: <MainChild />
},
// 略...
]
},
]);
跟vue-router一樣,被當為嵌套路由的父元件中,也需要加上一些東西才能把子路由顯示出來,react-router-dom是使用Outlet
,才可以在父元件中顯示子路由的內容。
import { Outlet, NavLink } from 'react-router-dom';
export default function Layout() {
return (
<div>
<Outlet />
</div>
)
}
最後呈現的效果也就會是這樣。
除了定義一般的路由外,也會需要處理使用者打了一些我們未定義好的網址,或是遇到某些網址需要被導向其他頁面的狀況,這時候需要再定義好的路由中,做一些其他的設定。
vue-router
在路由設定中加上動態路由搭配正規表達式的用法,讓vue-router去做匹配檢查
。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
// 略...
{
// :pathMatch是動態路由的部分,後面則是正規表達式的部分
path: '/:pathMatch(.*)*',
name: 'NotFoundPage',
component: NotFoundPage,
},
]
})
react-router-dom
react-router-dom的話,可以把path設定為*,當沒有匹配到patch,就會顯示NotFoundPage
。
<Routes>
// 略...
<Route path="*" element={<NotFoundPage />} />
</Routes>
用物件設定的寫法一樣是設定一個path為*的route。
const router = createHashRouter([
// 略 ...
{
path: '*',
element: <NotFoundPage />
}
]);
vue-router
如果要設定重新定向,可以使用redirect
。如果延續前面404頁面的設定的話,可以調整成這樣。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
// 略...
{
path: '/404',
name: 'NotFoundPage',
component: NotFoundPage,
},
{
path: '/:pathMatch(.*)*',
redirect: '/404',
},
]
})
react-router-dom
react-router-dom的重新導向是使用Navigate
,延續前面404頁面的設定的話,可以調整成這樣。
<Routes>
// 略...
<Route path="404" element={<NotFoundPage />} />
<Route path="*" element={<Navigate to="/404" replace />} />
</Routes>
使用物件設定,一樣也是用Navigate下去重新定向。
const router = createHashRouter([
// 略...
{
path: '404',
element: <NotFoundPage />
},
{
path: '*',
element: <Navigate to="/404" />
}
]);
在動態路由設定的部分,vue-router和react-router-dom都是透過「:」寫在路由的設定中。
例如以下這樣,就可以當id是動態的狀況下,還是會被導到對應的頁面。
path: ':id',
這部分跟之前提到過的lazy有關的部分,也就是讓路由對應到的元件可以用動態加載的方式載入。在看要怎麼做這部分的設定之前,可以先觀察我們原先的寫法,在前面例子的寫法,都沒有用到lazy loading的設定,所以不論是Vue專案或是React專案,在Dev Tools觀察的時候,都可以發現到剛進頁面,不管這個元件有沒有被使用在當前的畫面上,都有被載入。
如果是Vue的話,可以看到有一堆還沒使用到的Vue檔案。
如果是React的話,則可以看到有沒有用到的元件出現在bundle.js檔案裡面。
vue-router
vue-router是透過箭頭函式來讓路由對應的元件以lazy loading的方式加載。
import { createRouter, createWebHashHistory } from 'vue-router'
import MainPage from '../views/MainPage.vue'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'MainPage',
component: MainPage
},
{
path: '/page-a',
name: 'PageA',
component: () => import('../views/PageA.vue')
},
{
path: '/page-b',
name: 'PageB',
component: () => import('../views/PageB.vue')
},
// 略...
]
})
調整成箭頭函式之後,只有在使用到該元件時,才會載入對應的元件。
react-router-dom
react-router-dom則是用lazy搭配Suspense來讓路由元件以lazy loading的方式加載。
先來看Routes的寫法,主要是使用lazy來import我們要放在route中的元件。
import MainPage from './MainPage';
import { lazy } from 'react';
const PageA = lazy(() => import('./PageA'));
function App() {
return (
<div className="App">
<Routes>
<Route exact path="/" element={<MainPage />}></Route>
<Route path="page-a" element={<PageA />}></Route>
</Routes>
</div>
);
}
再到root的地方用Suspense把App包起來。
root.render(
<React.StrictMode>
<HashRouter>
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
</HashRouter>
</React.StrictMode>
);
使用物件定義路由的方式,也是一樣使用lazy搭配Suspense。
import { createHashRouter } from "react-router-dom";
import { lazy, Suspense } from 'react';
const PageA = lazy(() => import('../PageA'));
const Router = createHashRouter([
{
path: '/',
element: <MainPage />
},
{
path: 'page-a',
element: (
<Suspense>
<PageA />
</Suspense>
)
},
// 略...
]);
這樣調整過後,一樣也是只有使用到對應的頁面,才會把相對應的元件載入。
今天認識了react-dom以及react-router-dom,也從幾個平常我們在使用vue-router的時候,會用到的一些router基本用法來學習react-router-dom的使用。今天已經有先盤點出一些vue-router的常見用法,但還沒把這個清單中的項目,都用react-router-dom再看過一遍,明天會繼續來看剩下幾個常見的用法。